package com.zeng.stpro.modules.security.config;

import com.zeng.stpro.commons.utils.Constant;
import com.zeng.stpro.commons.utils.JwtUtils;
import com.zeng.stpro.commons.utils.R;
import com.zeng.stpro.commons.utils.ResponseUtils;
import com.zeng.stpro.modules.app.mapper.SysMenuMapper;
import com.zeng.stpro.modules.security.entity.SecurityUser;
import com.zeng.stpro.modules.security.filter.LoginCaptchaFilter;
import com.zeng.stpro.modules.security.filter.TokenAuthFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName SecurityConfig
 * @Description TODO
 * @Author zhijun zeng at 23:32 2021/2/3
 * @Version 1.0
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private RedisTemplate redisTemplate;
    private SysMenuMapper sysMenuMapper;

    @Autowired
    public SecurityConfig(UserDetailsService userDetailsService, RedisTemplate redisTemplate, SysMenuMapper sysMenuMapper) {
        this.userDetailsService = userDetailsService;
        this.redisTemplate = redisTemplate;
        this.sysMenuMapper = sysMenuMapper;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* 开启跨域共享    跨域伪造请求限制=无效 */
        http.cors().and().csrf().disable();

        /**
         * 页面报一个"Refused to display 'http://......' in a frame because it set 'X-Frame-Options' to 'DENY'. "错误
         * 需要配置如下, 在 IFRAME 中打开页面时方不会报上述错误
         */
        http.headers().frameOptions().disable();
        //X-Content-Type-Options 头设置允许加载静态资源文件，处理以下问题
        //来自“http://localhost:8080/api.js”的资源已被阻止，因为 MIME 类型（“application/json”）不匹配（X-Content-Type-Options: nosniff）。
        http.headers().contentTypeOptions().disable();

        //http.addFilterBefore(new LoginCaptchaFilter(authenticationFailureHandler()), UsernamePasswordAuthenticationFilter.class);

        http.authorizeRequests()
                .antMatchers("/**/favicon.ico").permitAll()//ICO
                .antMatchers("/api/admin/menu/nav").permitAll()//ICO
                .antMatchers("/captcha/**").permitAll()//验证码
                .antMatchers("/api/test/t1/**","/api/test/t2/**","/api/test/t6/**").permitAll()
                //.antMatchers("/api/test/t7","/api/test/t8","/api/test/t9").permitAll()
                .antMatchers("/login.html","/index.html").permitAll()
                .antMatchers("/api/admin/login","/api/admin/logout").permitAll()
                .antMatchers("/api/t/**").hasAnyAuthority("user:test")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/api/admin/login")
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .and()
                .logout()
                .addLogoutHandler(logoutHandler())              // 登出处理器
                .logoutSuccessHandler(logoutSuccessHandler())   // 登出成功处理器
                .logoutUrl("/api/admin/logout")
                //.logoutSuccessUrl("/login.html")
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler())
                .and()
                //.addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate))//认证过滤器
                .addFilter(new TokenAuthFilter(authenticationManager(), redisTemplate, sysMenuMapper))//授权过滤器
                .httpBasic();


//        http
//                .exceptionHandling()
//                .authenticationEntryPoint(new UnauthEntryPoint())  //匿名访问的处理
//                .and()
//                .authorizeRequests()
//                //.antMatchers("/login","/api/admin/logout").permitAll()
//                .anyRequest().authenticated()
//                .and().formLogin().loginProcessingUrl("/login")
//                .and()
//                .logout().logoutUrl("/api/admin/logout")  //用户登出路径
//                .addLogoutHandler(new TokenLogoutHandler(redisTemplate))  //用户登出的处理
//                .and()
//                .addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate))//登录过滤器
//                .addFilter(new TokenAuthFilter(authenticationManager(), redisTemplate))//授权过滤器
//                .httpBasic();


//        http.exceptionHandling()
//                .authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问
//                .and().csrf().disable()
//                .authorizeRequests()
//                .anyRequest().authenticated()
//                .and().logout().logoutUrl("/api/admin/logout") //退出路径
//                .addLogoutHandler(new TokenLogoutHandler(redisTemplate)).and()
//                .addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate))
//                .addFilter(new TokenAuthFilter(authenticationManager(), redisTemplate)).httpBasic();

        /* 限制 同一个账号只能一个用户使用 */
//        http.sessionManagement()
//                .maximumSessions(1)//同一账号同时登录最大用户数
//                .expiredSessionStrategy(customizeSessionInformationExpiredStrategy);//SESSION失效(账号被挤下线)处理逻辑
    }

    /**
     * 静态资源 地址过滤
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/page/**");// URL 以 /static/ 开头的过滤
        web.ignoring().antMatchers("/api/**/*.js");
        web.ignoring().antMatchers("/api/**/*.css");

    }


    // ==================================================================================================================

    /*登录成功处理器*/
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                System.out.println("登录成功");


                //认证成功，得到认证成功之后用户信息
                SecurityUser user = (SecurityUser)authentication.getPrincipal();
                //根据用户名生成token
                String token = JwtUtils.createToken(user.getCurrentUserInfo().getUsername());

                //把用户名称 放到redis
                Map<String, Object> userinfo = new HashMap<>();
                userinfo.put(Constant.RedisHashKey.USER.val(), user);
                userinfo.put(Constant.RedisHashKey.TOKEN.val(), token);
                userinfo.put(Constant.RedisHashKey.PROMS.val(), user.getPermissionValueList());
                redisTemplate.opsForHash().putAll(user.getCurrentUserInfo().getUsername(), userinfo);
                //httpServletRequest.getSession().setAttribute(user.getCurrentUserInfo().getUsername(), userinfo);//存放到Session中

                //返回token
                ResponseUtils.out(httpServletResponse, R.ok().data(Constant.TOKEN, token));
            }
        };
    }
    /*登录失败处理器*/
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                System.out.println("登录失败");
                ResponseUtils.out(httpServletResponse, R.ng("登录失败"));
            }
        };
    }
    /*登出处理器 -- 一般用于页面跳转使用*/
    @Bean
    public LogoutHandler logoutHandler() {
        return new LogoutHandler() {
            @Override
            public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
                System.out.println("登出处理器");
                // 清除 token
            }
        };
    }

    /*登出成功处理器  --  用于前后端分离*/
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                System.out.println("登出成功");
                //1 从header里面获取token
                //2 token不为空，移除token，从redis删除token
                String token = httpServletRequest.getHeader("token");
                if(token != null) {
                    //从token获取用户名
                    String username = JwtUtils.parseToken(token);
                    httpServletRequest.getSession().invalidate();//从session中删除
                    //redis中删除
                    redisTemplate.delete(username);
                    //移除
                    JwtUtils.removeToken(token);
                }

                ResponseUtils.out(httpServletResponse, R.ok("登出成功"));
                // 用于记录日志
            }
        };
    }

    /*匿名访问的处理*/
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                System.out.println("匿名访问");
                ResponseUtils.out(httpServletResponse, R.ng("匿名访问"));
            }
        };
    }

    /*没有权限的处理*/
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                System.out.println("没有权限访问");
                ResponseUtils.out(httpServletResponse, R.ng("没有权限访问"));
            }
        };
    }


}


